The System.Collections.Generic Namespace

You can find the bulk of the System.Collections.Generic namespace in the mscorlib.dll and System.dll assemblies. At the opening of this chapter, I briefly mentioned some of the core non-generic interfaces implemented by the non-generic collection classes. Not too surprisingly, the System.Collections.Generic namespace defines generic replacements for many of them.

In fact, you can find a number of the generic interfaces that extend their non-generic counterparts! This might seem odd; however, by doing so, implementing classes will also support the legacy functionally found in their non-generic siblings. For example, IEnumerable<T> extends IEnumerable. Table 10-3 documents the core generic interfaces you’ll encounter when working with the generic collection classes.

Note If you have worked with generics prior to .NET 4.0, you should be aware of the new ISet<T> and SortedSet<T> types, which you’ll learn more about later in this chapter.

Table 10-3. Key Interfaces Supported by Classes of System.Collections.Generic

System.Collections Interface Meaning in Life
ICollection<T> Defines general characteristics (e.g., size, enumeration, and thread safety) for all generic collection types.
IComparer<T> Defines a way to compare to objects.
IDictionary<TKey, TValue> Allows a generic collection object to represent its contents using key/value pairs.
IEnumerable<T> Returns the IEnumerator<T> interface for a given object.
IEnumerator<T> Enables foreach-style iteration over a generic collection.
IList<T> Provides behavior to add, remove, and index items in a sequential list of objects.
ISet<T> Provides the base interface for the abstraction of sets.

The System.Collections.Generic namespace also defines several classes that implement many of these key interfaces. Table 10-4 describes some commonly used classes of this namespace, the interfaces they implement, and their basic functionality.

Table 10-4. Classes of System.Collections.Generic

Generic Class Supported Key Interfaces Meaning in Life
Dictionary<TKey, TValue> ICollection<T>, IDictionary<TKey, TValue>, IEnumerable<T> This represents a generic collection of keys and values.
List<T> ICollection<T>, IEnumerable<T>, IList<T> This is a dynamically resizable sequential list of items.
LinkedList<T> ICollection<T>, IEnumerable<T> This represents a doubly linked list.
Queue<T> ICollection (not a typo! This is the non-generic collection interface), IEnumerable<T> This is a generic implementation of a first-in, first-out (FIFO) list.
SortedDictionary<TKey, TValue> ICollection<T>, IDictionary<TKey, TValue>, IEnumerable<T> This is a generic implementation of a sorted set of key/value pairs.
SortedSet<T> ICollection<T>, IEnumerable<T>, ISet<T> This represents a collection of objects that is maintained in sorted order with no duplication.
Stack<T> ICollection (not a typo! This is the non-generic collection interface), IEnumerable<T> This is a generic implementation of a last-in, first-out (LIFO) list.

The System.Collections.Generic namespace also defines many auxiliary classes and structures that work in conjunction with a specific container. For example, the LinkedListNode<T> type represents a node within a generic LinkedList<T>, the KeyNotFoundException exception is raised when attempting to grab an item from a container using a nonexistent key, and so forth.

It is also worth pointing out that mscorlib.dll and System.dll are not the only assemblies which add new types to the System.Collections.Generic namespace. For example, System.Core.dll adds the HashSet<T> class to the mix. Be sure to consult the .NET Framework 4.0 SDK documentation for full details of the System.Collections.Generic namespace.

In any case, your next task is to learn how to use some of these generic collection classes. Before you do however, allow me to illustrate a C# language feature (first introduced in .NET 3.5) that simplifies the way you populate generic (and non-generic) collection containers with data.

Understanding Collection Initialization Syntax

In Chapter 4, you learned about object initialization syntax, which allows you to set properties on a new variable at the time of construction. Closely related to this is collection initialization syntax. This C# language feature makes it possible to populate many containers (such as ArrayList or List<T>) with items by using syntax similar to what you use to populate a basic array.

Note You can only apply collection initialization syntax to classes that support an Add() method, which is formalized by the ICollection<T>/ICollection interfaces.

Consider the following examples:

// Init a standard array.
int[] myArrayOfInts = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Init a generic List<> of ints.
List<int> myGenericList = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Init an ArrayList with numerical data.
ArrayList myList = new ArrayList { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

If your container is managing a collection of classes or a structure, you can blend object initialization syntax with collection initialization syntax to yield some functional code. You might recall the Point class from Chapter 5, which defined two properties named X and Y. If you wish to build a generic List<T> of Point objects, you can write the following:

List<Point> myListOfPoints = new List<Point>
{
    new Point { X = 2, Y = 2 },
    new Point { X = 3, Y = 3 },
    new Point(PointColor.BloodRed){ X = 4, Y = 4 }
};

foreach (var pt in myListOfPoints)
{
    Console.WriteLine(pt);
}

Again, the benefit of this syntax is that you save yourself numerous keystrokes. While the nested curly brackets can become difficult to read if you don’t mind your formatting, imagine the amount of code that would be required to fill the following List<T> of Rectangles if you did not have collection initialization syntax (you might recall from Chapter 4 that you created a Rectangle class that contained two properties encapsulating Point objects):

List<Rectangle> myListOfRects = new List<Rectangle>
{
    new Rectangle {TopLeft = new Point { X = 10, Y = 10 },
        BottomRight = new Point { X = 200, Y = 200}},
    new Rectangle {TopLeft = new Point { X = 2, Y = 2 },
        BottomRight = new Point { X = 100, Y = 100}},
    new Rectangle {TopLeft = new Point { X = 5, Y = 5 },
        BottomRight = new Point { X = 90, Y = 75}}
};

foreach (var r in myListOfRects)
{
    Console.WriteLine(r);
}

Working with the List<T> Class

Create a brand new Console Application project named FunWithGenericCollections. This project type automatically references mscorlib.dll and System.dll, so you have access to a majority of the common generic collection classes. Also note that your initial C# code file already imports the System.Collections.Generic namespace.

The first generic class you will examine is the List<T>, which you’ve already seen once or twice in this chapter. The List<T> class is bound to be your most frequently used type in the System.Collections.Generic namespace because it allows you to resize the contents dynamically. To illustrate the basics of this type, ponder the following method in your Program class, which leverages List<T> to manipulate the set of Person objects seen earlier in this chapter; you might recall that these Person objects defined three properties (Age, FirstName, and LastName) and a custom ToString() implementation:

private static void UseGenericList()
{
    // Make a List of Person objects, filled with
    // collection / object init syntax.
    List<Person> people = new List<Person>()
    {
        new Person {FirstName= "Homer", LastName="Simpson", Age=47},
        new Person {FirstName= "Marge", LastName="Simpson", Age=45},
        new Person {FirstName= "Lisa", LastName="Simpson", Age=9},
        new Person {FirstName= "Bart", LastName="Simpson", Age=8}
    };

    // Print out # of items in List.
    Console.WriteLine("Items in list: {0}", people.Count);

    // Enumerate over list.
    foreach (Person p in people)
        Console.WriteLine(p);

    // Insert a new person.
    Console.WriteLine("\n->Inserting new person.");
    people.Insert(2, new Person { FirstName = "Maggie", LastName = "Simpson", Age = 2 });

    Console.WriteLine("Items in list: {0}", people.Count);

    // Copy data into a new array.
    Person[] arrayOfPeople = people.ToArray();
    for (int i = 0; i < arrayOfPeople.Length; i++)
    {
        Console.WriteLine("First Names: {0}", arrayOfPeople[i].FirstName);
    }
}

Here you use initialization syntax to populate your List<T> with objects, as a shorthand notation for calling Add() n number of times. Once you print out the number of items in the collection (as well as enumerate over each item), you invoke Insert(). As you can see, Insert() allows you to plug a new item into the List<T> at a specified index.

Finally, notice the call to the ToArray() method, which returns an array of Person objects based on the contents of the original List<T>. From this array, you loop over the items again using the array’s indexer syntax. If you call this method from within Main(), you get the following output:

***** Fun with Generic Collections *****

Items in list: 4
Name: Homer Simpson, Age: 47
Name: Marge Simpson, Age: 45
Name: Lisa Simpson, Age: 9
Name: Bart Simpson, Age: 8

->Inserting new person.
Items in list: 5
First Names: Homer
First Names: Marge
First Names: Maggie
First Names: Lisa
First Names: Bart

The List<T> class defines many additional members of interest, so be sure to consult the .NET Framework 4.0 SDK documentation for more information. Next, let’s look at a few more generic collections, specifically Stack<T>, Queue<T> and SortedSet<T>. This should get you in a great position to understand your basic choices regarding how to hold your custom application data.

Working with the Stack<T> Class

The Stack<T> class represents a collection that maintains items using a last-in, first-out manner. As you might expect, Stack<T> defines members named Push() and Pop() to place items onto or remove items from the stack. The following method creates a stack of Person objects:

static void UseGenericStack()
{
    Stack<Person> stackOfPeople = new Stack<Person>();
    stackOfPeople.Push(new Person
        { FirstName = "Homer", LastName = "Simpson", Age = 47 });
    stackOfPeople.Push(new Person
        { FirstName = "Marge", LastName = "Simpson", Age = 45 });
    stackOfPeople.Push(new Person
        { FirstName = "Lisa", LastName = "Simpson", Age = 9 });

    // Now look at the top item, pop it, and look again.
    Console.WriteLine("First person is: {0}", stackOfPeople.Peek());
    Console.WriteLine("Popped off {0}", stackOfPeople.Pop());

    Console.WriteLine("\nFirst person is: {0}", stackOfPeople.Peek());
    Console.WriteLine("Popped off {0}", stackOfPeople.Pop());

    Console.WriteLine("\nFirst person item is: {0}", stackOfPeople.Peek());
    Console.WriteLine("Popped off {0}", stackOfPeople.Pop());

    try
    {
        Console.WriteLine("\nnFirst person is: {0}", stackOfPeople.Peek());
        Console.WriteLine("Popped off {0}", stackOfPeople.Pop());
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine("\nError! {0}", ex.Message);
    }
}

Here, you build a stack that contains three people, added in the order of their first names: Homer, Marge, and Lisa. As you peek into the stack, you will always see the object at the top first; therefore, the first call to Peek() reveals the third Person object. After a series of Pop() and Peek() calls, the stack eventually empties, at which time additional Peek() and Pop() calls raise a system exception. You can see the output for this here:

***** Fun with Generic Collections *****

First person is: Name: Lisa Simpson, Age: 9
Popped off Name: Lisa Simpson, Age: 9

First person is: Name: Marge Simpson, Age: 45
Popped off Name: Marge Simpson, Age: 45

First person item is: Name: Homer Simpson, Age: 47
Popped off Name: Homer Simpson, Age: 47

Error! Stack empty.

Working with the Queue<T> Class

Queues are containers that ensure items are accessed in a first-in, first-out manner. Sadly, we humans are subject to queues all day long: lines at the bank, lines at the movie theater, and lines at the morning coffeehouse. When you need to model a scenario in which items are handled on a first-come, firstserved basis, you will find the Queue<T> class fits the bill. In addition to the functionality provided by the supported interfaces, Queue defines the key members shown in Table 10-5.

Table 10-5. Members of the Queue<T> Type

Select Member of Queue<T> Meaning in Life
Dequeue() Removes and returns the object at the beginning of the Queue<T>.
Enqueue() Adds an object to the end of the Queue<T>.
Peek() Returns the object at the beginning of the Queue<T> without removing it.

Now let’s put these methods to work. You can begin by leveraging your Person class again and building a Queue<T> object that simulates a line of people waiting to order coffee. First, assume you have the following static helper method:

static void GetCoffee(Person p)
{
    Console.WriteLine("{0} got coffee!", p.FirstName);
}

Now assume you have this additional helper method, which calls GetCoffee() internally:

static void UseGenericQueue()
{
    // Make a Q with three people.
    Queue<Person> peopleQ = new Queue<Person>();
    peopleQ.Enqueue(new Person {FirstName= "Homer",
        LastName="Simpson", Age=47});
    peopleQ.Enqueue(new Person {FirstName= "Marge",
        LastName="Simpson", Age=45});
    peopleQ.Enqueue(new Person {FirstName= "Lisa",
        LastName="Simpson", Age=9});

    // Peek at first person in Q.
    Console.WriteLine("{0} is first in line!", peopleQ.Peek().FirstName);

    // Remove each person from Q.
    GetCoffee(peopleQ.Dequeue());
    GetCoffee(peopleQ.Dequeue());
    GetCoffee(peopleQ.Dequeue());

    // Try to de-Q again?
    try
    {
        GetCoffee(peopleQ.Dequeue());
    }
    catch(InvalidOperationException e)
    {
        Console.WriteLine("Error! {0}", e.Message);
    }
}

Here you insert three items into the Queue<T> class using its Enqueue() method. The call to Peek() allows you to view (but not remove) the first item currently in the Queue. Finally, the call to Dequeue() removes the item from the line and sends it into the GetCoffee() helper function for processing. Note that if you attempt to remove items from an empty queue, a runtime exception is thrown. Here is the output you receive when calling this method:

***** Fun with Generic Collections *****

Homer is first in line!
Homer got coffee!
Marge got coffee!
Lisa got coffee!
Error! Queue empty.

Working with the SortedSet<T> Class

The final generic collection class you will look was introduced with the release of .NET 4.0. The SortedSet<T> class is useful because it automatically ensures that the items in the set are sorted when you insert or remove items. However, you do need to inform the SortedSet<T> class exactly how you want it to sort the objects, by passing in as a constructor argument an object that implements the generic IComparer<T> interface.

Begin by creating a brand new class named SortPeopleByAge, which implements IComparer<T>, where T is of type Person. Recall that this interface defines a single method named Compare(), where you can author whatever logic you require for the comparison. Here is a simple implementation of this class:

class SortPeopleByAge : IComparer<Person>
{
    public int Compare(Person firstPerson, Person secondPerson)
    {
        if (firstPerson.Age > secondPerson.Age)
            return 1;
        if (firstPerson.Age < secondPerson.Age)
            return -1;
        else
            return 0;
    }
}

Now update your Program class with the following new method, which I assume you will call from Main():

private static void UseSortedSet()
{
    // Make some people with different ages.
    SortedSet<Person> setOfPeople = new SortedSet<Person>(new SortPeopleByAge())
    {
        new Person {FirstName= "Homer", LastName="Simpson", Age=47},
        new Person {FirstName= "Marge", LastName="Simpson", Age=45},
        new Person {FirstName= "Lisa", LastName="Simpson", Age=9},
        new Person {FirstName= "Bart", LastName="Simpson", Age=8}
    };

    // Note the items are sorted by age!
    foreach (Person p in setOfPeople)
    {
        Console.WriteLine(p);
    }
    Console.WriteLine();

    // Add a few new people, with various ages.
    setOfPeople.Add(new Person { FirstName = "Saku", LastName = "Jones", Age = 1 });
    setOfPeople.Add(new Person { FirstName = "Mikko", LastName = "Jones", Age = 32 });

        // Still sorted by age!
    foreach (Person p in setOfPeople)
    {
        Console.WriteLine(p);
    }
}

When you run your application, the listing of objects is now always ordered based on the value of the Age property, regardless of the order you inserted or removed objects:

***** Fun with Generic Collections *****

Name: Bart Simpson, Age: 8
Name: Lisa Simpson, Age: 9
Name: Marge Simpson, Age: 45
Name: Homer Simpson, Age: 47

Name: Saku Jones, Age: 1
Name: Bart Simpson, Age: 8
Name: Lisa Simpson, Age: 9
Name: Mikko Jones, Age: 32
Name: Marge Simpson, Age: 45
Name: Homer Simpson, Age: 47

Awesome! At this point, you should feel more comfortable, but just about the benefits of generic programming, but also with using generic types in the .NET base class libraries. To conclude this chapter, you will learn how to build your own custom generic types and generic methods, as well as why you might want to.